package appClientModule;

import java.beans.FeatureDescriptor;
import java.util.*;

/**
 * Trains a Reinforcement Learner to play against 3 set strategy AIs
 */
public class Trainer {
	private final double alpha;
	private final double discount;
	private final double greedy;
	private final int training;
	private final Map<ShitHeadAI, Hand> dealtCards = new HashMap<ShitHeadAI, Hand>();
	private Deque<ShitHeadAI> players;
	private Queue<Card> deck;
	private List<Card> pile;
	private String report;

	/**
	 * 
	 * @param greedy
	 *            Percent (0-100) of exploitation
	 * @param training
	 *            Number of games to play as training
	 */
	public Trainer(double alpha, double discount, double greedy, int training) {
		this.alpha = alpha;
		this.discount = discount;
		this.greedy = greedy;
		this.training = training;
	}

	/**
	 * Run multiple training games with the reinforcement learning AI
	 */
	public ReinforcementLearningAI trainReinforcementLearner() {
		ReinforcementLearningAI rlAI = new ReinforcementLearningAI(alpha,
				discount, greedy);
		rlAI.doreset(1);

		for (int i = 0; i < training; i++) {
			try {
				trainSingleGame(rlAI);
			} catch (RuntimeException e) {
				// game move limit reached, play another game
				i--;
			}
		}

		// switch to 100% greedy and test
		rlAI.setGreedyProb(100);
		int[] results = new int[4];

		for (int i = 0; i < 100; i++) {
			try {
				int result = trainSingleGame(rlAI);
				results[result - 1]++;
			} catch (RuntimeException e) {
				// game move limit reached, play another game
				i--;
			}
		}

		report = "Training with " + greedy + "% greedy for " + training
				+ " games.\n"
				+ "Results for 100 games played at 100% greedy:\n"
				+ "1st\t2nd\t3rd\t4th\n" + results[0] + "\t" + results[1]
				+ "\t" + results[2] + "\t" + results[3]
				+ "\nFeature Weights:\n";
		for (int i = 0; i < rlAI.featureWeights.length; i++) {
			report += "F[" + i + "] = " + rlAI.featureWeights[i] + "\n";
		}

		return rlAI;
	}

	/**
	 * Run a single training game with the reinforcement learning AI
	 */
	private int trainSingleGame(ReinforcementLearningAI rlAI) {
		players = createPlayers(rlAI);
		deck = createShuffledDeck();
		dealCards();
		pile = new ArrayList<Card>();
		int gameMoveLimit = 500;

		while (players.size() > 1 && gameMoveLimit > 0) {
			gameMoveLimit--;
			ShitHeadAI ai = players.poll();
			Hand hand = dealtCards.get(ai);

			String move = ai.basicMove(hand,
					pile.toArray(new Card[deck.size()]), pile.size());

			String[] command = move.split(":");
			if (command[1].equals("pickup")) {
				handlePickup(hand);
				players.offerLast(ai);
			} else if (command[1].equals("faceup")) {
				if (command[2].equals("multi")) {
					// get card numbers
					int[] cardNums = new int[Integer.parseInt(command[3])];
					for (int i = 0; i < cardNums.length; i++) {
						cardNums[i] = Integer.parseInt(command[4 + i]);
					}

					// remove cards from hand and add to pile
					for (int cardNum : cardNums) {
						for (int i = 0; i < 3; i++) {
							Card card = hand.getFaceUp(i);
							if (card != null && card.getNumber() == cardNum) {
								hand.removeFaceUp(i);
								pile.add(card);
								break;
							}
						}
					}
				} else {
					int cardNum = Integer.parseInt(command[2]);
					for (int i = 0; i < 3; i++) {
						Card card = hand.getFaceUp(i);
						if (card != null && card.getNumber() == cardNum) {
							hand.removeFaceUp(i);
							pile.add(card);
							break;
						}
					}
				}

				handleBurntPile(ai);
			} else if (command[1].equals("facedown")) {
				if (command[2].equals("pickup")) {
					// move non-playable card to hand
					int cardNum = Integer.parseInt(command[3]);
					for (int i = 0; i < 3; i++) {
						Card card = hand.getFaceDown(i);
						if (card != null && card.getNumber() == cardNum) {
							hand.removeFaceDown(i);
							hand.addCard(card);
							break;
						}
					}

					handlePickup(hand);
					players.offerLast(ai);
				} else {
					// remove card from hand and add to pile
					int cardNum = Integer.parseInt(command[2]);
					for (int i = 0; i < 3; i++) {
						Card card = hand.getFaceDown(i);
						if (card != null && card.getNumber() == cardNum) {
							hand.removeFaceDown(i);
							pile.add(card);
							break;
						}
					}

					handleBurntPile(ai);
				}

				Integer result = handleLastCardPlayed(ai, hand);
				if (result != null) {
					return result;
				}
			} else if (command[1].equals("multi")) {
				// get card numbers
				int[] cardNums = new int[Integer.parseInt(command[2])];
				for (int i = 0; i < cardNums.length; i++) {
					cardNums[i] = Integer.parseInt(command[3 + i]);
				}

				// remove cards from hand and add to pile
				for (int cardNum : cardNums) {
					for (int i = 0; i < hand.length(); i++) {
						Card card = hand.getCard(i);
						if (card.getNumber() == cardNum) {
							hand.removeCard(i);
							pile.add(card);
							break;
						}
					}
				}

				handleBurntPile(ai);
				handlePickupThree(hand);
				Integer result = handleLastCardPlayed(ai, hand);
				if (result != null) {
					return result;
				}
			} else { // player plays a single card from their hand
				int cardNum = Integer.parseInt(command[1]);

				// remove card from hand and add to pile
				for (int i = 0; i < hand.length(); i++) {
					Card card = hand.getCard(i);
					if (card.getNumber() == cardNum) {
						hand.removeCard(i);
						pile.add(card);
						break;
					}
				}

				handleBurntPile(ai);
				handlePickupThree(hand);
				Integer result = handleLastCardPlayed(ai, hand);
				if (result != null) {
					return result;
				}
			}
		}

		if (gameMoveLimit <= 0) {
			throw new RuntimeException("Game Move Limit Reached");
		}
		
		return 4;
	}

	private void handlePickup(Hand hand) {
		for (Card card : pile) {
			hand.addCard(card);
		}

		pile.clear();
	}

	private void handlePickupThree(Hand hand) {
		// pickup if less than 3 cards
		while (!deck.isEmpty() && hand.length() <= 3) {
			hand.addCard(deck.poll());
		}
	}

	private void handleBurntPile(ShitHeadAI ai) {
		// check for burnt pile
		if (burnt(pile)) {
			pile.clear();
			players.offerFirst(ai);
		} else {
			players.offerLast(ai);
		}
	}

	private Integer handleLastCardPlayed(ShitHeadAI ai, Hand hand) {
		// check if last card played
		if (lastCardPlayed(hand)) {
			players.remove(ai);

			// check game result
			if (ai instanceof ReinforcementLearningAI) {
				return 4 - players.size();
			}
		}

		return null;
	}

	private Deque<ShitHeadAI> createPlayers(ReinforcementLearningAI rlAI) {
		Deque<ShitHeadAI> players = new LinkedList<ShitHeadAI>();
		Random random = new Random();
		int rlAIposition = random.nextInt(4);

		for (int i = 0; i < 4; i++) {
			if (i == rlAIposition) {
				players.offer(rlAI);
			} else {
				players.offer(new GameAI(false, false));
			}
		}
		return players;
	}

	private Queue<Card> createShuffledDeck() {
		LinkedList<Card> deck = new LinkedList<Card>();
		for (int i = 1; i <= 52; i++) {
			deck.add(new Card(i));
		}

		Collections.shuffle(deck);
		return deck;
	}

	private void dealCards() {
		for (ShitHeadAI ai : players) {
			dealtCards.put(ai, new Hand());
		}

		for (int i = 0; i < 40; i++) {
			ShitHeadAI ai = players.poll();
			Hand hand = dealtCards.get(ai);
			Card card = deck.poll();
			hand.deal(card);
			players.offer(ai);
		}
	}

	private boolean burnt(List<Card> pile) {
		return lastCardIs10(pile) || lastCardsAreFourOfAKind(pile);
	}

	private boolean lastCardIs10(List<Card> pile) {
		return pile.size() > 0 && pile.get(pile.size() - 1).getValue() == 10;
	}

	private boolean lastCardsAreFourOfAKind(List<Card> pile) {
		if (pile.size() >= 4) {
			int value = pile.get(pile.size() - 4).getValue();
			for (int i = pile.size() - 3; i < pile.size(); i++) {
				if (pile.get(i).getValue() != value) {
					return false;
				}
			}

			return true;
		}

		return false;
	}

	private boolean lastCardPlayed(Hand hand) {
		return hand.getFaceDown(0) == null && hand.getFaceDown(1) == null
				&& hand.getFaceDown(2) == null && hand.getCard(0) == null;
	}

	public String getTrainingReport() {
		return report;
	}

}
